package com.ruoyi.common.utils;

import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.model.*;
import com.aliyuncs.exceptions.ClientException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.time.Duration;
import java.util.Date;
import java.util.Objects;
import java.util.UUID;

/**
 * 通用OSS工具类（基于阿里云OSS实现，可扩展至其他厂商）
 * 支持文件上传、下载、删除、查询信息等功能
 */
@Slf4j
@Component
public class OSSUtils {

    /**
     * OSS配置参数（从配置文件注入）
     */
    @Value("${oss.endpoint}")
    private String endpoint;

    @Value("${oss.access-key-id}")
    private String accessKeyId;

    @Value("${oss.access-key-secret}")
    private String accessKeySecret;

    @Value("${oss.bucket-name}")
    private String bucketName;

    @Value("${oss.domain:}")
    private String domain; // 自定义域名，为空则使用默认域名

    @Value("${oss.default-folder:}")
    private String defaultFolder; // 默认存储文件夹

    @Value("${oss.private:false}")
    private boolean isPrivate; // 是否私有桶

    /**
     * OSS客户端实例
     */
    private OSS ossClient;

    /**
     * 初始化OSS客户端
     */
    @PostConstruct
    public void init() {
        try {
            ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
            // 检查桶是否存在
            if (!ossClient.doesBucketExist(bucketName)) {
                throw new OssException("OSS bucket不存在: " + bucketName);
            }
            log.info("OSS客户端初始化成功，bucket: {}", bucketName);
        } catch (Exception e) {
            log.error("OSS客户端初始化失败", e);
            throw new OssException("OSS客户端初始化失败: " + e.getMessage());
        }
    }

    /**
     * 销毁OSS客户端
     */
    @PreDestroy
    public void destroy() {
        if (ossClient != null) {
            ossClient.shutdown();
            log.info("OSS客户端已关闭");
        }
    }

    /**
     * 上传文件（MultipartFile）
     * @param file 上传的文件
     * @param folder 存储文件夹（为空则使用默认文件夹）
     * @return 上传结果
     */
    public OssFileInfo upload(MultipartFile file, String folder) {
        Objects.requireNonNull(file, "上传文件不能为空");
        if (file.isEmpty()) {
            throw new OssException("上传文件内容为空");
        }

        try {
            return upload(file.getInputStream(),
                    file.getOriginalFilename(),
                    file.getContentType(),
                    folder);
        } catch (IOException e) {
            throw new OssException("文件读取失败: " + e.getMessage(), e);
        }
    }

    /**
     * 上传文件（InputStream）
     * @param inputStream 文件输入流
     * @param originalFilename 原始文件名
     * @param contentType 文件MIME类型
     * @param folder 存储文件夹
     * @return 上传结果
     */
    public OssFileInfo upload(InputStream inputStream, String originalFilename,
                              String contentType, String folder) {
        Objects.requireNonNull(inputStream, "输入流不能为空");
        Objects.requireNonNull(originalFilename, "原始文件名不能为空");

        try {
            // 1. 处理存储路径
            String actualFolder = getActualFolder(folder);
            String fileKey = generateFileKey(originalFilename, actualFolder);

            // 2. 设置文件元数据
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(contentType != null ? contentType : getDefaultContentType(originalFilename));
            metadata.setContentLength(inputStream.available());

            // 3. 执行上传
            PutObjectResult result = ossClient.putObject(bucketName, fileKey, inputStream, metadata);
            log.info("文件上传成功，key: {}, etag: {}", fileKey, result.getETag());

            // 4. 构建返回结果
            return buildOssFileInfo(originalFilename, fileKey, metadata);
        } catch (Exception e) {
            log.error("文件上传失败", e);
            throw new OssException("文件上传失败: " + e.getMessage(), e);
        }
    }

    /**
     * 上传字节数组
     * @param bytes 字节数组
     * @param originalFilename 原始文件名
     * @param folder 存储文件夹
     * @return 上传结果
     */
    public OssFileInfo upload(byte[] bytes, String originalFilename, String folder) {
        Objects.requireNonNull(bytes, "字节数组不能为空");
        return upload(new ByteArrayInputStream(bytes), originalFilename, null, folder);
    }

    /**
     * 下载文件
     * @param fileKey OSS中的文件Key
     * @return 文件输入流
     */
    public InputStream download(String fileKey) {
        Objects.requireNonNull(fileKey, "文件Key不能为空");

        try {
            if (!ossClient.doesObjectExist(bucketName, fileKey)) {
                throw new OssException("文件不存在: " + fileKey);
            }

            OSSObject ossObject = ossClient.getObject(bucketName, fileKey);
            return ossObject.getObjectContent();
        } catch (Exception e) {
            log.error("文件下载失败，key: {}", fileKey, e);
            throw new OssException("文件下载失败: " + e.getMessage(), e);
        }
    }

    /**
     * 删除文件
     * @param fileKey OSS中的文件Key
     * @return 是否删除成功
     */
    public boolean delete(String fileKey) {
        Objects.requireNonNull(fileKey, "文件Key不能为空");

        try {
            if (!ossClient.doesObjectExist(bucketName, fileKey)) {
                log.warn("文件不存在，无需删除: {}", fileKey);
                return false;
            }

            ossClient.deleteObject(bucketName, fileKey);
            log.info("文件删除成功，key: {}", fileKey);
            return true;
        } catch (Exception e) {
            log.error("文件删除失败，key: {}", fileKey, e);
            throw new OssException("文件删除失败: " + e.getMessage(), e);
        }
    }

    /**
     * 获取文件信息
     * @param fileKey OSS中的文件Key
     * @return 文件信息
     */
    public OssFileInfo getFileInfo(String fileKey) {
        Objects.requireNonNull(fileKey, "文件Key不能为空");

        try {
            if (!ossClient.doesObjectExist(bucketName, fileKey)) {
                throw new OssException("文件不存在: " + fileKey);
            }

            ObjectMetadata metadata = ossClient.getObjectMetadata(bucketName, fileKey);
            String originalName = extractOriginalName(fileKey);
            return buildOssFileInfo(originalName, fileKey, metadata);
        } catch (Exception e) {
            log.error("获取文件信息失败，key: {}", fileKey, e);
            throw new OssException("获取文件信息失败: " + e.getMessage(), e);
        }
    }

    /**
     * 生成签名URL（用于私有桶临时访问）
     * @param fileKey OSS中的文件Key
     * @param expire 过期时间
     * @return 签名URL
     */
    public String generateSignedUrl(String fileKey, Duration expire) {
        Objects.requireNonNull(fileKey, "文件Key不能为空");
        Objects.requireNonNull(expire, "过期时间不能为空");

        try {
            // 计算过期时间点
            Date expiration = new Date(System.currentTimeMillis() + expire.toMillis());

            // 生成URL
            URL url = ossClient.generatePresignedUrl(bucketName, fileKey, expiration);
            return url.toString();
        } catch (Exception e) {
            log.error("生成签名URL失败，key: {}", fileKey, e);
            throw new OssException("生成签名URL失败: " + e.getMessage(), e);
        }
    }

    /**
     * 生成默认的文件访问URL
     * @param fileKey OSS中的文件Key
     * @return 访问URL
     */
    public String getFileUrl(String fileKey) {
        if (StringUtils.isNotBlank(domain)) {
            return domain + "/" + fileKey;
        } else {
            return "https://" + bucketName + "." + endpoint + "/" + fileKey;
        }
    }

    // ------------------------------ 私有工具方法 ------------------------------

    /**
     * 获取实际存储文件夹
     */
    private String getActualFolder(String folder) {
        // 处理文件夹路径，确保以/结尾
        String actualFolder = StringUtils.isBlank(folder) ? defaultFolder : folder;
        if (StringUtils.isNotBlank(actualFolder) && !actualFolder.endsWith("/")) {
            actualFolder += "/";
        }
        return actualFolder;
    }

    /**
     * 生成文件存储Key（路径+文件名）
     */
    private String generateFileKey(String originalFilename, String folder) {
        // 提取文件扩展名
        String extension = getFileExtension(originalFilename);
        // 生成唯一文件名（UUID）
        String uniqueName = UUID.randomUUID().toString() + (extension.isEmpty() ? "" : "." + extension);
        // 组合路径
        return folder + uniqueName;
    }

    /**
     * 提取文件扩展名
     */
    private String getFileExtension(String filename) {
        if (filename == null || !filename.contains(".")) {
            return "";
        }
        return filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
    }

    /**
     * 提取原始文件名（从fileKey中）
     */
    private String extractOriginalName(String fileKey) {
        // 实际项目中可根据需要存储原始文件名，这里简单返回
        return fileKey.substring(fileKey.lastIndexOf("/") + 1);
    }

    /**
     * 获取默认的Content-Type
     */
    private String getDefaultContentType(String filename) {
        String extension = getFileExtension(filename);
        switch (extension) {
            case "jpg":
            case "jpeg":
                return "image/jpeg";
            case "png":
                return "image/png";
            case "gif":
                return "image/gif";
            case "pdf":
                return "application/pdf";
            case "doc":
            case "docx":
                return "application/msword";
            default:
                return "application/octet-stream";
        }
    }

    /**
     * 构建文件信息对象
     */
    private OssFileInfo buildOssFileInfo(String originalFilename, String fileKey, ObjectMetadata metadata) {
        OssFileInfo fileInfo = new OssFileInfo();
        fileInfo.setFileKey(fileKey);
        fileInfo.setOriginalName(originalFilename);
        fileInfo.setFileName(fileKey.substring(fileKey.lastIndexOf("/") + 1));
        fileInfo.setFileType(metadata.getContentType());
        fileInfo.setFileSize(metadata.getContentLength());
        fileInfo.setUploadTime(new Date());
        fileInfo.setEtag(metadata.getETag());

        // 根据桶类型设置访问URL
        if (isPrivate) {
            // 私有桶生成临时URL（默认1小时有效期）
            fileInfo.setDownloadUrl(generateSignedUrl(fileKey, Duration.ofHours(1)));
        } else {
            fileInfo.setDownloadUrl(getFileUrl(fileKey));
        }

        return fileInfo;
    }

    /**
     * OSS文件信息封装类
     */
    public static class OssFileInfo {
        private String fileKey;         // OSS中的文件唯一标识
        private String fileName;        // 存储的文件名
        private String originalName;    // 原始文件名
        private String fileType;        // 文件类型(MIME)
        private Long fileSize;          // 文件大小(字节)
        private Date uploadTime;        // 上传时间
        private String etag;            // 文件ETag
        private String downloadUrl;     // 下载URL

        // Getter和Setter
        public String getFileKey() { return fileKey; }
        public void setFileKey(String fileKey) { this.fileKey = fileKey; }
        public String getFileName() { return fileName; }
        public void setFileName(String fileName) { this.fileName = fileName; }
        public String getOriginalName() { return originalName; }
        public void setOriginalName(String originalName) { this.originalName = originalName; }
        public String getFileType() { return fileType; }
        public void setFileType(String fileType) { this.fileType = fileType; }
        public Long getFileSize() { return fileSize; }
        public void setFileSize(Long fileSize) { this.fileSize = fileSize; }
        public Date getUploadTime() { return uploadTime; }
        public void setUploadTime(Date uploadTime) { this.uploadTime = uploadTime; }
        public String getEtag() { return etag; }
        public void setEtag(String etag) { this.etag = etag; }
        public String getDownloadUrl() { return downloadUrl; }
        public void setDownloadUrl(String downloadUrl) { this.downloadUrl = downloadUrl; }
    }

    /**
     * OSS自定义异常
     */
    public static class OssException extends RuntimeException {
        public OssException(String message) {
            super(message);
        }

        public OssException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    public static void main(String[] args) throws ClientException {
        // https://aobayijia.oss-cn-shenzhen.aliyuncs.com/contract/39ee21bb-7717-4c5d-b416-21f478de066f.png
        // Endpoint以华东1（杭州）为例，其它Region请按实际情况填写。关于其他Region对应的Endpoint信息，请参见访问域名和数据中心。
        String endpoint = "https://oss-cn-shenzhen.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前，请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写您的AccessKeyId。
        String accessKeyId = "LTAI5tBeagSeMrJje1pf7LCj";
        // 填写您的AccessKeySecret。
        String accessKeySecret = "X85WnvwbcFm3hPumcwh1GWxgXnBREa" ;
        // 填写Bucket名称，例如examplebucket。
        String bucketName = "aobayijia";
        // 填写Object完整路径，例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
        String objectName = "contract/39ee21bb-7717-4c5d-b416-21f478de066f.png";
        // 填写Bucket所在地域。以华东1（杭州）为例，Region填写为cn-hangzhou。
        String region = "cn-hangzhou";
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // ossObject包含文件所在的存储空间名称、文件名称、文件元数据以及一个输入流。
            OSSObject ossObject = ossClient.getObject(bucketName, objectName);
            InputStream inputStream = ossObject.getObjectContent();
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            // 读取文件内容到字节数组。
            byte[] readBuffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = inputStream.read(readBuffer)) != -1) {
                byteArrayOutputStream.write(readBuffer, 0, bytesRead);
            }
            // 获取最终的字节数组。
            byte[] fileBytes = byteArrayOutputStream.toByteArray();
            // 打印字节数组的长度。
            System.out.println("Downloaded file size: " + fileBytes.length + " bytes");
            // 数据读取完成后，获取的流必须关闭，否则会造成连接泄漏，导致请求无连接可用，程序无法正常工作。
            inputStream.close();
            byteArrayOutputStream.close();
            // ossObject对象使用完毕后必须关闭，否则会造成连接泄漏，导致请求无连接可用，程序无法正常工作。
            ossObject.close();
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (Throwable ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}
